Part 4: 勾配ベクトルの集計処理をつけたしたFederated Learning

復讐: 本チュートリアルシリーズのパート2で、私たちはFederated Learningを使ってトイモデル(初歩的なモデル)のトレーニングを行いました。その際、データ所有者たちは自分たちの勾配ベクトルを見るためにモデル所有者を信頼する必要がありました。

要約: このチュートリアルでは、パート3で紹介した高度なツールを使うことで、学習済みモデルをモデル所有者(ここでは私たち)に戻す前に、モデルの集計を行います。モデルの平均化は、信頼できる第三者に任せます。

集計担当の信頼できる第三者を本チュートリアルではセキュアワーカー(secure worker)と呼びます。

こうすることで、誰がどのようなウェイト(勾配ベクトル)を送ってきたのかを知りうるのはセキュアワーカーだけになります。モデル所有者はモデルのどの部分がアップデートされたのかは知る事ができるかもしれませんが、どのワーカー(BobなのかAliceなのか)がその部分もモデルをアップデートしたのかを知る事はできません。これはデータの秘匿性がワンランク上がったことを意味します。

Authors:


In [ ]:
import torch
import syft as sy
import copy
hook = sy.TorchHook(torch)
from torch import nn, optim

Step 1: データ所有者の作成

まず、なんらかのデータを持つデータ所有者を2名(ここではBobとAlice)を作成します。また、"secure_worker"という名前のセキュアワーカーを1名作成しておきます。実運用では、セキュアワーカーは(IntelのSGXのような)セキュリティ的に堅牢なハードウェア、あるいは単純に信頼できる代理人が担当します。


In [ ]:
# データ所有者を2名(BobとAlice)作成します。

bob = sy.VirtualWorker(hook, id="bob")
alice = sy.VirtualWorker(hook, id="alice")
secure_worker = sy.VirtualWorker(hook, id="secure_worker")


# トイデータセット(初歩的なモデルで使用するデータセット)の準備
data = torch.tensor([[0,0],[0,1],[1,0],[1,1.]], requires_grad=True)
target = torch.tensor([[0],[0],[1],[1.]], requires_grad=True)

# データを各ワーカーへ送り、そのポインタを取得しておきます。
bobs_data = data[0:2].send(bob)
bobs_target = target[0:2].send(bob)

alices_data = data[2:].send(alice)
alices_target = target[2:].send(alice)

Step 2: モデルの定義

この例では、シンプルなリニアモデルを使います。nn.Linearを使用します。


In [ ]:
# トイモデルを初期化
model = nn.Linear(2,1)

Step 3: モデルのコピーをBobとAliceへ送る

次にモデルのコピーをBobとAliceへ送る必要があります。 そうする事で、BobとAliceは彼らのデータセットでそれぞれ学習を進める事ができるようになります。


In [ ]:
bobs_model = model.copy().send(bob)
alices_model = model.copy().send(alice)

bobs_opt = optim.SGD(params=bobs_model.parameters(),lr=0.1)
alices_opt = optim.SGD(params=alices_model.parameters(),lr=0.1)

In [ ]:

Step 4: BobとAliceのモデルをそれぞれ(並行して)学習

セキュアワーカーを使ってFederated Learningを行う際は、まず(セキュアワーカーが集計する前に)、各データ所有者(ワーカー)が自分のデータでモデルの学習を行います。ある程度トレーニングループを回してモデルをデータにフィットさせます。


In [ ]:
for i in range(10):

    # Bobのモデルを学習
    bobs_opt.zero_grad()
    bobs_pred = bobs_model(bobs_data)
    bobs_loss = ((bobs_pred - bobs_target)**2).sum()
    bobs_loss.backward()

    bobs_opt.step()
    bobs_loss = bobs_loss.get().data

    # Aliceのモデルを学習
    alices_opt.zero_grad()
    alices_pred = alices_model(alices_data)
    alices_loss = ((alices_pred - alices_target)**2).sum()
    alices_loss.backward()

    alices_opt.step()
    alices_loss = alices_loss.get().data
    
    print("Bob:" + str(bobs_loss) + " Alice:" + str(alices_loss))

Step 5: 全ての学習済みモデルをセキュアワーカーへ送る

現在、各データ所有者(ワーカー)は部分的に学習したモデルを持っているので、それらの学習済みウェイトをセキュアに集計しましょう。これはBobとAliceに彼らのデータをセキュアワーカー(信頼できる第三者、サーバー等)へ送ってもらう事で実現できます。

注記 この際、われわれはPySyftのAPIを使ってモデルを直接セキュアワーカーへ送ります。モデル所有者(この場合は私たち)はそのモデルを見る事はありません。


In [ ]:
alices_model.move(secure_worker)

In [ ]:
bobs_model.move(secure_worker)

Step 6: モデル(のウェイトを)集計する# Step 6: Average the Models

最後に、トレーニングエポックの最後のステップとして、BobとAliceの学習済みモデルを集計し、結果のウェイトをモデル所有者(この場合は私たち)のモデルのウェイトにセットします。


In [ ]:
with torch.no_grad():
    model.weight.set_(((alices_model.weight.data + bobs_model.weight.data) / 2).get())
    model.bias.set_(((alices_model.bias.data + bobs_model.bias.data) / 2).get())

ループの処理を追加して全体を整理

後はループの処理を追加するだけです。


In [ ]:
iterations = 10
worker_iters = 5

for a_iter in range(iterations):
    
    bobs_model = model.copy().send(bob)
    alices_model = model.copy().send(alice)

    bobs_opt = optim.SGD(params=bobs_model.parameters(),lr=0.1)
    alices_opt = optim.SGD(params=alices_model.parameters(),lr=0.1)

    for wi in range(worker_iters):

        # Bobのモデルをトレーニング
        bobs_opt.zero_grad()
        bobs_pred = bobs_model(bobs_data)
        bobs_loss = ((bobs_pred - bobs_target)**2).sum()
        bobs_loss.backward()

        bobs_opt.step()
        bobs_loss = bobs_loss.get().data

        # Aliceのモデルをトレーニング
        alices_opt.zero_grad()
        alices_pred = alices_model(alices_data)
        alices_loss = ((alices_pred - alices_target)**2).sum()
        alices_loss.backward()

        alices_opt.step()
        alices_loss = alices_loss.get().data
    
    alices_model.move(secure_worker)
    bobs_model.move(secure_worker)
    with torch.no_grad():
        model.weight.set_(((alices_model.weight.data + bobs_model.weight.data) / 2).get())
        model.bias.set_(((alices_model.bias.data + bobs_model.bias.data) / 2).get())
    
    print("Bob:" + str(bobs_loss) + " Alice:" + str(alices_loss))

最後にテストデータを使って、モデルが正しく学習したことを確認しましょう。このトイモデルのケースでは、トレーニングで使用したものと同じデータセットをテストデータにも使用します。しかし、実際のプロジェクトでは、学習で使わなかったデータをテストデータとして使います。


In [ ]:
preds = model(data)
loss = ((preds - target) ** 2).sum()

In [ ]:
print(preds)
print(target)
print(loss.data)

このトイモデルの例では、Federated Learningを使ったモデルは一般的なPytorchの手法(ローカルだけで)だけで学習したものと比べると精度が低く、学習が不十分な感じがします。しかし、各ワーカー(今回のケースではAliceとBob)のデータを露出せずにモデルの学習を行えています。また、各ワーカーの環境にて部分的に学習されたモデルをセキュアワーカー環境化で集計することにより、モデル所有者が学習途中のモデルからデータを推測することも防いでいます。

次のチュートリアルでは、信頼できるセキュアワーカーが、集計作業を勾配ベクトルから直接行えるよう変更を加えます。そうすることで、より高い制度で集計ができ、精度の向上も期待できます。


In [ ]:

おめでとうございます!コミュニティに入ろう!

本チュートリアルを完了しました。おめでとうございます!もし、このチュートリアルを気に入って、プライバシーに配慮した非中央集権的なAI技術や付随する(データやモデルの)サプライチェーンにご興味があって、プロジェクトに参加したいと思われるなら、以下の方法で可能です。

PySyftのGitHubレポジトリにスターをつける

一番簡単に貢献できる方法はこのGitHubのレポジトリにスターを付けていただくことです。スターが増えると露出が増え、より多くのデベロッパーにこのクールな技術の事を知って貰えます。

Slackに入る

最新の開発状況のトラッキングする一番良い方法はSlackに入ることです。 下記フォームから入る事ができます。 http://slack.openmined.org

コードプロジェクトに参加する

コミュニティに貢献する一番良い方法はソースコードのコントリビューターになることです。PySyftのGitHubへアクセスしてIssueのページを開き、"Projects"で検索してみてください。参加し得るプロジェクトの状況を把握することができます。また、"good first issue"とマークされているIssueを探す事でミニプロジェクトを探すこともできます。

寄付

もし、ソースコードで貢献できるほどの時間は取れないけど、是非何かサポートしたいという場合は、寄付をしていただくことも可能です。寄附金の全ては、ハッカソンやミートアップの開催といった、コミュニティ運営経費として利用されます。

OpenMined's Open Collective Page


In [ ]: